# -*- coding: utf-8 -*-
# Copyright (C) 2007 Boris Dušek
# See the file LICENCE in the top-level directory of this distribution
# for licensing information.
# vim:ts=4:sw=4:et

import struct

class MemoryEngine:
    """Base class for memory access."""

    def read(self,address):
        raise NotImplementedError

    def write(self,address,byte):
        raise NotImplementedError

    def read_many(self,address,count):
        "".join([self.read(address+pos) for pos in range(count)])

    def write_many(self,address,bytes):
        for pos,byte in enumerate(bytes):
            self.write(address+pos,byte)

class DecoratorMemoryEngine(MemoryEngine):
    """Base class for memory access decorators.

    Decorators are classes that will not write directly to the final storage,
    instead they provide some kind of data processing on the data before it is
    written. It is well possible that there will be even more than 1 layer of
    decorators on top of the real memory engine that writes data somewhere.

    """

    def __init__(self,engine):
        """engine - the engine on top of which to operate"""
        self.engine = engine

    def read(self,address):
        return self.engine.read(address)

    def write(self,address,byte):
        self.engine.write(address,byte)

    def read_many(self,address,count):
        return self.engine.read_many(address,count)

    def write_many(self,address,bytes):
        self.engine.write_many(address,bytes)


class EndianMemoryEngine(DecoratorMemoryEngine):

    LITTLE_ENDIAN,BIG_ENDIAN=range(2)

    _end_fmts = {
            LITTLE_ENDIAN: '<',
            BIG_ENDIAN   : '>',
        }

    _size_fmts = {
            2: 'H',
            4: 'I'
        }

    def __init__(self,engine,endianness=LITTLE_ENDIAN):
        self.engine = engine
        self.end = endianness

    def _fmt_string(self,size,signed):
        size_fmt = self._size_fmts[size]
        if signed:
            size_fmt = size_fmt.lower()
        fmt = self._end_fmts[self.end] + size_fmt
        return fmt

    def read_multibyte(self,address,size,signed=False):
        fmt = self._fmt_string(size,signed)
        return struct.unpack(fmt,self.read_many(address,size))[0]

    def write_multibyte(self,address,value,size,signed=False):
        fmt = self._fmt_string(size,signed)
        bytes = struct.pack(fmt,value)
        self.write_many(address,bytes)
